package org.biogroovy.search;

import groovy.transform.AutoClone
import java.util.Map.Entry;

/**
 * This class contains a collection of search parameters.
 *
 */
@AutoClone
class SearchParamSet implements Serializable{

	/** A map of search parameter names to search parameters. */
	private Map<String, SearchParam> searchParams = new TreeMap<>();

	/**
	 * Constructor.
	 */
	public SearchParamSet(){
	}

	/**
	 * Gets a map of search parameter names to search parameters.
	 * @return
	 */
	public Map<String, SearchParam> getSearchParams(){
		return searchParams;
	}

	/** 
	 * Sets the search parameters.
	 * @param params a list of search parameters.
	 */
	public void setSearchParams(List<SearchParam> params){
		for(SearchParam param : params){
			searchParams.put(param.name, param);
		}
	}

	/**
	 * Sets the search parameter map.
	 * @param paramMap the parameter map.
	 */
	public void setSearchParams(Map<String, SearchParam> paramMap){
		this.searchParams = paramMap;
	}

	/**
	 * Adds a search parameter(s) to the parameter set.
	 * 
	 * @param params the search parameter(s) to be added.
	 */
	public void addAll(SearchParam... params){
		for(SearchParam param : params){
			searchParams.put(param.name, param);
		}
	}

	/**
	 * This method sets the parameter value.
	 * 
	 * @param paramName the name of the parameter.
	 * @param value the value.
	 * @throws RuntimeException if the parameter does not exist in the param set.
	 */
	public void setParameterValue(String paramName, String value){
		SearchParam param = searchParams.get(paramName);
		if (param != null){
			param.setValue(value);
		}else {
			throw new RuntimeException("Unknown parameter: " + paramName);
		}
	}

	/**
	 * This method sets all of the parameter values references in the param map.
	 * @param paramMap a map of parameter names and values.
	 */
	public void setParameterValues(Map<String, String> paramMap){
		for(Entry<String, String> entry : paramMap.entrySet()){
			setParameterValue(entry.key, entry.value);
		}
	}

	/**
	 * This method gets all search parameters.
	 * @return a collection of search parameters.
	 */
	public Collection<SearchParam> getParameters(){
		return searchParams.values();
	}

	/**
	 * Gets the search parameter.
	 * @param name the name of the search parameter.
	 * @return the search parameter, or null if not found.
	 */
	public SearchParam getParam(String name){
		return searchParams.get(name);
	}


	/**
	 * This method gets a set of all parameter key-value entries.
	 * @return a set of all parameter key-value entries.
	 */
	public Set<Entry<String, SearchParam>> entrySet(){
		return searchParams.entrySet();
	}

	/**
	 * Gets the specified search parameter
	 * @param paramName the parameter name
	 * @return the SearchParam or null if not found.
	 */
	public SearchParam get(String paramName){
		return searchParams.get(paramName);
	}

	/**
	 * Determines whether or not a parameter with a given name exists in the parameter set.
	 * @param paramName the name of the parameter.
	 * @return true of the parameter exists, false otherwise
	 */
	public boolean hasParam(String paramName){
		return searchParams.containsKey(paramName);
	}

	/**
	 * Determines if the param set has a parameter of a given name with a non-null value.
	 * @param paramName the name of the parameter.
	 * @return true, if a parameter with a given name has a non-null value.
	 */
	public boolean hasPopulatedParam(String paramName){
		boolean hasPopParam = false;
		SearchParam param = getParam(paramName);
		if (param != null){
			return param.hasValue();
		}
		return hasPopParam;
	}

	/**
	 * Determines if the param set contains sub-terms.
	 * @return true if the param set has sub-terms.
	 */
	public boolean hasSubTerms(){
		for(SearchParam param : searchParams.values()){
			if (param.isSubTerm){
				return true;
			}
		}
		return false;
	}

	/**
	 * Determines if the param set contains non-null sub-terms
	 * @return true, if the param set contains non-null sub-terms
	 */
	public boolean hasPopulatedSubTerms(){
		for(SearchParam param : searchParams.values()){
			if (param.isSubTerm && param.hasValue()){
				return true;
			}
		}
		return false;
	}

	/**
	 * This method is responsible for getting the main "term" parameter.
	 * The term is the primary search term and is known by different names
	 * in different search engines.
	 * 
	 * @return the 'term' parameter, or null if not found.
	 */
	public SearchParam getTerm(){
		SearchParam termParam = null;
		for(SearchParam currParam : searchParams.values()){
			if (currParam.isTerm){
				termParam = currParam;
				break;
			}
		}

		return termParam;
	}

	/**
	 * Sets the main search term value.
	 * @param term the main term value.
	 */
	public void setTerm(String term){
		SearchParam termParam = getTerm();
		termParam.setValue(term);
	}

	/**
	 * Gets a set of all subterms.
	 * @return a set of all subterms, or an empty set if there are no subterms.
	 */
	public Set<SearchParam> getSubTerms(){
		Set<SearchParam> subTerms = new HashSet<>();
		for(SearchParam currParam :searchParams.values()){
			if (currParam.isSubTerm){
				subTerms.add(currParam);
			}
		}
		return subTerms;
	}

	/**
	 * Determines if the param set is empty.
	 * @return true, if the param set is empty; false, otherwise.
	 */
	public boolean isEmpty(){
		return searchParams.isEmpty();
	}

	@Override
	public String toString(){
		StringBuilder sb = new StringBuilder();
		sb.append("SearchParamSet[\n");
		searchParams.values().each  { SearchParam param ->
			sb.append(param.toString());
		}
		sb.append("]")
	}
}
